package vg.services.user_interface_manager;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.*;

import vg.services.data_base_manager.interfaces.IGraphDataBase;
import vg.services.main_manager.MainManager;
import vg.services.plugin_manager.event_and_request.event.AUIEvent;
import vg.services.plugin_manager.event_and_request.request.AUIRequest;
import vg.services.plugin_manager.event_and_request.request.EUIRequestType;
import vg.services.plugin_manager.event_and_request.request.IUIRequestOwner;
import vg.services.user_interface_manager.additional_swing_components.SimpleStatusBar;
import vg.services.user_interface_manager.interfaces.IUserInterfaceManager;
import vg.services.user_interface_manager.interfaces.AUserInterfaceElement;
import vg.services.user_interface_manager.interfaces.IUserInterfaceObserver;
import vg.services.user_interface_manager.interfaces.UserInterfaceObserverAdapter;
import vg.services.user_interface_manager.realization.InstrumentPanel;
import vg.services.user_interface_manager.realization.UserInterfacePanel;

/**
 * This class realizes user interface and use swing for it.
 * @author tzolotuhin
 */
public class UserInterfaceManagerManager implements IUserInterfaceManager {
	// Components
	private JMenu fileMenu, editMenu, windowMenu, otherMenu, helpMenu, exportMenu, importMenu;
	private JMenuItem quitMenuItem;
	private JSplitPane splitFirst, splitSecond, splitThird;
	private JLabel progressName;
	// Main Components
	private final JFrame frame;
	private final JMenuBar menuBar;
	private final InstrumentPanel instruments;
	private final SimpleStatusBar statusBar;
	private final UserInterfacePanel navigatorPanel, desktopPanel, attributePanel, minimapPanel;
	private final IUserInterfaceObserver eventListener; // it need for UIEventChangeUIstyle and other
	//-------------------------------------------------------------------------
	private final static String DEF_WINDOWS_SIZE_X = "WindowSizeX";
	private final static String DEF_WINDOWS_SIZE_Y = "WindowSizeY";
	private final static String DEF_WINDOWS_POS_X = "WindowPosX";
	private final static String DEF_WINDOWS_POS_Y = "WindowPosY";
	private Integer windowSizeX, windowSizeY;
	private Integer windowPosX, windowPosY;
	//------------------------------------------------------------------------- 
	private WorkThread workThread;
	private JProgressBar progressBar;
	private final Timer progressTimer;
	//-------------------------------------------------------------------------
	public UserInterfaceManagerManager(final IGraphDataBase theGraphDataBase, final String title) {
		//this.frameTitle = title;
		this.frame = new JFrame(title);
		// Creating of window
		this.loadWindowOptions();
		this.frame.setSize(this.windowSizeX, this.windowSizeY);
		this.frame.setLocation(this.windowPosX, this.windowPosY);
		this.frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		this.frame.setLayout(new BorderLayout());
		this.frame.addComponentListener(new ComponentAdapter() {
			public void componentResized(ComponentEvent e) {
				super.componentResized(e);
			    Component c = (Component)e.getSource();
		        Integer sizeX = c.getSize().width;
		        Integer sizeY = c.getSize().height;
		        MainManager.config.setProperty(DEF_WINDOWS_SIZE_X, sizeX.toString());
		        MainManager.config.setProperty(DEF_WINDOWS_SIZE_Y, sizeY.toString());
			}
			public void componentMoved(ComponentEvent e) {
				super.componentMoved(e);
			    Component c = (Component)e.getSource();
		        Integer posX = c.getLocation().x;
		        Integer posY = c.getLocation().y;
		        MainManager.config.setProperty(DEF_WINDOWS_POS_X, posX.toString());
		        MainManager.config.setProperty(DEF_WINDOWS_POS_Y, posY.toString());
			}
		});
		this.frame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				quit();
			}
		});
		// Creating of  menu
		this.menuBar = new JMenuBar();
		this.frame.setJMenuBar(menuBar);
		
		fileMenu  = new JMenu("File");
		editMenu  = new JMenu("Edit");
		windowMenu  = new JMenu("Window");
		otherMenu = new JMenu("Other");
		exportMenu = new JMenu("Export to");
		exportMenu.setEnabled(false);
        importMenu = new JMenu("Import from");
        importMenu.setEnabled(false);
		helpMenu = new JMenu("Help");
		
		this.quitMenuItem  = new JMenuItem("Quit");
		
		this.quitMenuItem.setIcon(new ImageIcon("./data/resources/textures/quit.png"));
		// Creating components
		this.navigatorPanel = new UserInterfacePanel("Navigator", null);
		this.desktopPanel = new UserInterfacePanel("Desktop", null);
		this.attributePanel = new UserInterfacePanel("Attribute panel", null);
		this.minimapPanel = new UserInterfacePanel("Mini map", null);
		
		this.splitFirst = new JSplitPane(JSplitPane.VERTICAL_SPLIT, this.navigatorPanel.getView(), this.minimapPanel.getView());
		this.splitSecond = new JSplitPane(JSplitPane.VERTICAL_SPLIT, this.desktopPanel.getView(), this.attributePanel.getView());
		this.splitThird = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, this.splitFirst, this.splitSecond);
		
		this.splitFirst.setResizeWeight(0.7);
		this.splitFirst.setDividerSize(8);
		this.splitFirst.setOneTouchExpandable(true);
		this.splitSecond.setResizeWeight(0.7);
		this.splitSecond.setDividerSize(8);
		this.splitSecond.setOneTouchExpandable(true);
		this.splitThird.setResizeWeight(0.2);
		this.splitThird.setDividerSize(8);
		this.splitThird.setOneTouchExpandable(true);

		this.instruments = new InstrumentPanel();
		this.statusBar = new SimpleStatusBar();
		this.progressBar = new JProgressBar(0,100);
		this.progressBar.setMaximumSize(new Dimension(100,this.statusBar.getPreferredSize().height-2));
		this.progressName = new JLabel();
		this.statusBar.add(progressName);
		this.statusBar.add(progressBar);
		this.progressTimer = new Timer(500, new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {	
						progressName.setText(MainManager.progressManager.getTaskName());
						progressBar.setValue((int) MainManager.progressManager.updateProgress());
						boolean visible = MainManager.progressManager.getTaskCount() > 0;
						progressName.setVisible(visible);
						progressBar.setVisible(visible);
					}
				});
			}
		});
		progressTimer.start();		
		this.workThread = new WorkThread();
		Thread t = new Thread(this.workThread);
		t.setDaemon(true);
		t.start();

		// Package UI
		frame.add(this.instruments.getView(),BorderLayout.NORTH);
		frame.add(this.splitThird,BorderLayout.CENTER);
		frame.add(this.statusBar,BorderLayout.SOUTH);
		
        // Adding of menu bar
		fileMenu.add(quitMenuItem);
		
		menuBar.add(fileMenu);
		menuBar.add(editMenu);
		menuBar.add(windowMenu);
		menuBar.add(helpMenu);

		// Adding mnemonic for menus
		fileMenu.setMnemonic('f');
		editMenu.setMnemonic('e');
		helpMenu.setMnemonic('h');	
		quitMenuItem.setMnemonic('q');
		quitMenuItem.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				quit();
			}
		});
		quitMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, KeyEvent.ALT_DOWN_MASK));

		// Add event and request listener for this window
		eventListener = new UserInterfaceObserverAdapter() {
            @Override
            public void dispatchEvent(AUIEvent event) {
                switch (event.getType()) {
                    case DEF_CLOSE_PROGRAM: {
                        quit();
                        break;
                    }

                    case DEF_CHANGE_UI_STYLE: {
                        SwingUtilities.updateComponentTreeUI(frame.getContentPane());
                        instruments.updateUIStyle();
                        break;
                    }

                    default: break;
                }
            }
		};
		this.addObserver(this.eventListener);
		// Run
		this.frame.setVisible(true);
	}

    @Override
	public void addMenuItem(JMenuItem item, String place) {
		if(item == null || place == null) {
			MainManager.logger.printError("[FAIL] addMenuItem, item = null || place = null");
			return;
		}

        MainManager.logger.printDebug("[PROCESS] Add menu item  = " + item.getText() + ", place = " + place);
		if(place.equalsIgnoreCase("File")) {
			fileMenu.remove(quitMenuItem);
			fileMenu.remove(importMenu);
			fileMenu.remove(exportMenu);
			fileMenu.add(item);
			fileMenu.add(importMenu);
			fileMenu.add(exportMenu);
			fileMenu.add(quitMenuItem);
		} else  if(place.equalsIgnoreCase("Edit")) {
			editMenu.add(item);
		} else if(place.equalsIgnoreCase("Window")) {
			windowMenu.add(item);
		} else if(place.equalsIgnoreCase("Help")) {
			helpMenu.add(item);
		} else if (place.equalsIgnoreCase("Export")) {
			exportMenu.add(item);
            exportMenu.setEnabled(true);
		} else if (place.equalsIgnoreCase("Import")) {
			importMenu.add(item);
            importMenu.setEnabled(true);
		} else {
			otherMenu.add(item);
			MainManager.logger.printError("[BAD] addMenuItem, item = " + item.getText() + ", place = " + place + "(This place is not found.)");
		}
	}

    @Override
    public void addElement(AUserInterfaceElement element, String place) {
		if(element == null || place == null) {
            MainManager.logger.printError("[FAIL] addElement, element = null || place = null");
			return;
		}

        MainManager.logger.printDebug("[PROCESS] Add element, element = " +element.getTitleComponent() + ", place = " + place);
        if(place.equalsIgnoreCase("MiniMap")) {
			minimapPanel.setMainTab(element);
		} else if(place.equalsIgnoreCase("Navigator")) {
			navigatorPanel.addTab(element);
		} else if(place.equalsIgnoreCase("Desktop")) {
			desktopPanel.setMainTab(element);
		} else if(place.equalsIgnoreCase("AttributePanel")) {
			attributePanel.addTab(element);
		} else {
			navigatorPanel.addTab(element);
            MainManager.logger.printError("[BAD] addElement, element = " + element.getTitleComponent() + ", place = " + place + "(This place is not found.)");
        }
	}

    @Override
    public void addObserver(UserInterfaceObserverAdapter adapter) {
        workThread.addElement(adapter);
    }

    @Override
    public void addObserver(IUserInterfaceObserver observer) {
        workThread.addElement(observer);
    }

    @Override
    public void addInstrument(AUserInterfaceElement element) {
		instruments.addInstrument(element.getView());
	}

    @Override
    public void sendRequest(AUIRequest request) {
		workThread.addRequest(request);
	}

    @Override
    public void sendEvent(AUIEvent event) {
		workThread.addEvent(event);
	}
	
	@Override
	public JFrame getMainFrame() {
		return frame;
	}
	
	public void quit() {
		MainManager.graphDataBase.close();
		this.frame.dispose();
		this.progressTimer.stop();
		this.workThread.close();
		MainManager.threadPool.shutdown();
		for(Window w : Window.getWindows()) {
			w.dispose();
		}
	}
	/**
	 * Load window options.
	 */
	private void loadWindowOptions() {
		String wSizeStrX = MainManager.config.getProperty(DEF_WINDOWS_SIZE_X);
		String wSizeStrY = MainManager.config.getProperty(DEF_WINDOWS_SIZE_Y);
		String wPosStrX = MainManager.config.getProperty(DEF_WINDOWS_POS_X);
		String wPosStrY = MainManager.config.getProperty(DEF_WINDOWS_POS_Y);
		//set size X(width of window)
		if(wSizeStrX == null) {
			this.windowSizeX = 800;
			MainManager.config.setProperty(DEF_WINDOWS_SIZE_X, this.windowSizeX.toString());
		} else {
			try {
				this.windowSizeX = new Integer(wSizeStrX);
			} catch(NumberFormatException ex) {
				this.windowSizeX = 800;
				MainManager.config.setProperty(DEF_WINDOWS_SIZE_X, this.windowSizeX.toString());
			}
		}
		//set size Y(height of window)
		if(wSizeStrY == null) {
			this.windowSizeY = 600;
			MainManager.config.setProperty(DEF_WINDOWS_SIZE_Y, this.windowSizeY.toString());			
		} else {
			try {
				this.windowSizeY = new Integer(wSizeStrY);
			} catch(NumberFormatException ex) {
				this.windowSizeY = 600;
				MainManager.config.setProperty(DEF_WINDOWS_SIZE_Y, this.windowSizeY.toString());
			}			
		}
		//set position X
		if(wPosStrX == null) {
			this.windowPosX = 200;
			MainManager.config.setProperty(DEF_WINDOWS_POS_X, this.windowPosX.toString());
		} else {
			try {
				this.windowPosX = new Integer(wPosStrX);
			} catch(NumberFormatException ex) {
				this.windowPosX = 800;
				MainManager.config.setProperty(DEF_WINDOWS_POS_X, this.windowPosX.toString());
			}
		}
		//set position Y
		if(wPosStrY == null) {
			this.windowPosY = 200;
			MainManager.config.setProperty(DEF_WINDOWS_POS_Y, this.windowPosY.toString());
		} else {
			try {
				this.windowPosY = new Integer(wPosStrY);
			} catch(NumberFormatException ex) {
				this.windowPosY = 800;
				MainManager.config.setProperty(DEF_WINDOWS_POS_Y, this.windowPosY.toString());
			}
		}
	} 
}
/**
 * This class is main class for user interface. This class provides
 * communications for all modules in the program.
 * @author tzolotuhin
 */
class WorkThread implements Runnable {
	// Main data
    private List<IUserInterfaceObserver> arrayOfListeners; // array of listeners for different modules(or plugins) in decoders.
	private List<AUIEvent> arrayOfEvent; // array of events by modules(or plugins).
	private List<AUIRequest> arrayOfRequest; // array of requests by modules(or plugins).
	private boolean close = false;

	public WorkThread() {
		this.arrayOfListeners = new ArrayList<IUserInterfaceObserver>();
		this.arrayOfEvent = new ArrayList<AUIEvent>();
		this.arrayOfRequest = new ArrayList<AUIRequest>();
	}
	/**
	 * This method adds new module.
	 */
	public void addElement(IUserInterfaceObserver e) {
		synchronized (arrayOfListeners) {
			arrayOfListeners.add(e);
		}
	}
	/**
	 * This methods adds new event. 
	 * This event will be sent to all decoders(or user) modules.
	 */
	public void addEvent(AUIEvent event) {
		synchronized (arrayOfEvent) {
			arrayOfEvent.add(event);
		}
	}
	/**
	 * This methods adds new request. 
	 * This request will be sent to all decoders(or user) modules.
	 */
	public void addRequest(AUIRequest request) {
		synchronized (arrayOfRequest) {
			arrayOfRequest.add(request);
		}
	}
	/**
	 * It's an endless cycle.
	 */
	public void run() {
		//condition of exit 
		while(!this.close) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException ex) {}
			
			List<AUIEvent>bufEvents = null;
			List<AUIRequest>bufRequests = null;

            // copy events & requests
            synchronized (arrayOfEvent) {
				bufEvents = new ArrayList<AUIEvent>(arrayOfEvent);
				arrayOfEvent.clear();
			}

            synchronized (arrayOfRequest) {
				bufRequests = new ArrayList<AUIRequest>(arrayOfRequest);
				arrayOfRequest.clear();
			}

			// circle for events
			for(final AUIEvent bufEvent : bufEvents) {
				synchronized (arrayOfListeners) {
					for(final IUserInterfaceObserver buf : arrayOfListeners) {
						SwingUtilities.invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    long start = System.currentTimeMillis();
                                    buf.dispatchEvent(bufEvent);
                                    long finish = System.currentTimeMillis();

                                    if(finish - start > 1000) {
                                        MainManager.logger.printDebug("[EVENT] [" + bufEvent.getType().toString() + "] [" + buf.getClass().getName() + "] "
                                                + " work time: " + (finish - start) / 1000.0 + " sec");
                                    }
                                } catch (Throwable ex) {
                                    if(MainManager.logger != null) {
                                        MainManager.logger.printException(ex);
                                    }
                                    MainManager.windowMessage.errorMessage("Fail.\nException : " + ex.getMessage(), "Thread error");
                                }
                            }
                        });
					}
				}
			}

			// circle for requests
			for(final AUIRequest bufRequest : bufRequests) {
				synchronized (arrayOfListeners) {
					for(final IUserInterfaceObserver buf : arrayOfListeners) {
						SwingUtilities.invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    long start = System.currentTimeMillis();
                                    buf.dispatchRequest(bufRequest);
                                    long finish = System.currentTimeMillis();
                                    if(finish - start > 1000) {
                                        MainManager.logger.printDebug("[REQUEST] [" + bufRequest.getType().toString() + "] [" + buf.getClass().getName() + "] "
                                                + " work time: " + (finish - start) / 1000.0 + " sec");
                                    }
                                } catch (Throwable ex) {
                                    if(MainManager.logger != null) {
                                        MainManager.logger.printException(ex);
                                    }
                                    MainManager.windowMessage.errorMessage("Fail.\nException : " + ex.getMessage(), "Thread error");
                                }
                            }
                        });
					}
					try {
						IUIRequestOwner owner = bufRequest.getOwner();
						if(owner != null) {
							owner.callRequestOwner(new AUIRequest(EUIRequestType.PASS, null){});
						}
					} catch(Throwable ex) {
						MainManager.logger.printException(ex);
					}
				}
			}
		}
	}

	public void close() {
		close = true;
	}
}
